/*
 * 为游戏服务器提供数据支持服务的线程
 */
package labox.innovation.loginserver;

import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Set;

import javolution.util.FastSet;
import labox.innovation.config.Config;
import labox.innovation.loginserver.GameServerTable.GameServerInfo;
import labox.innovation.loginserver.gameserverpackets.ChangeAccessLevel;
import labox.innovation.loginserver.gameserverpackets.CharacterCountsRequest;
import labox.innovation.loginserver.gameserverpackets.GameServerAuth;
import labox.innovation.loginserver.gameserverpackets.PlayerAuthRequest;
import labox.innovation.loginserver.gameserverpackets.PlayerInGame;
import labox.innovation.loginserver.gameserverpackets.PlayerLogout;
import labox.innovation.loginserver.gameserverpackets.ServerStatus;
import labox.innovation.loginserver.loginserverpackets.AuthResponse;
import labox.innovation.loginserver.loginserverpackets.InitLS;
import labox.innovation.loginserver.loginserverpackets.KeepAlive;
import labox.innovation.loginserver.loginserverpackets.KickPlayer;
import labox.innovation.loginserver.loginserverpackets.LoginServerFail;
import labox.innovation.loginserver.loginserverpackets.PlayerAuthResponse;
import labox.innovation.loginserver.loginserverpackets.ServerBasePacket;
import labox.innovation.util.Util;
import labox.innovation.util.crypt.NewCrypt;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author -Wooden-
 * @author KenM
 * 
 */

public class GameServerThread extends Thread {

	protected static final Logger _log = LoggerFactory.getLogger(GameServerThread.class.getName());

	private Socket _socket;

	private InputStream _in;

	private OutputStream _out;

	private String _connectionIp;

	/**
	 * 此线程维护的游戏服务器信息
	 */
	private GameServerInfo _gsi;

	/**
	 * 正式的登录到游戏服务器的账户集合
	 */
	private Set<String> _accountsOnGameServer = new FastSet<String>();

	private String _connectionIPAddress;

	@Override
	public void run() {
		_connectionIPAddress = _socket.getInetAddress().getHostAddress();
		if (GameServerThread.isBannedGameserverIP(_connectionIPAddress)) { //-- 判断游戏服务器地址是否被屏蔽，注意：该方法暂时没有具体实现，默认返回false。
			_log.info("GameServerRegistration: IP Address " + _connectionIPAddress + " is on Banned IP list.", Config.SERVER_ID);
			forceClose(LoginServerFail.REASON_IP_BANNED); //-- 被屏蔽强制关闭连接
			// ensure no further processing for this connection
			return;
		}

		InitLS startPacket = new InitLS(); //-- 组初始连接验证包
		try {
			sendPacket(startPacket);//发送最初连接的验证包，包含协议版本信息

			int lengthHi = 0;
			int lengthLo = 0;
			int length = 0;
			boolean checksumOk = false;
			for (;;) {
				try {
					lengthLo = _in.read();
					if (lengthLo < 0) {
						_log.info("LoginServerThread: Login terminated the connection. GameServer " + (getServerId() != -1 ? "[" + getServerId() + "] " + _gsi.getExternalHost() : "(" + _connectionIPAddress + ")"), Config.SERVER_ID);
						break;
					}
					lengthHi = _in.read();
					length = (lengthHi << 8) | lengthLo; //-- 获取数据包长度

					byte[] data = new byte[length - 2]; //-- 创建存储数据包体部分缓冲区

					int n = 0;
					while (n < data.length) {
						int count = _in.read(data, n, data.length - n);
						if (count < 0)
							throw new EOFException();
						n += count;
					}

					checksumOk = NewCrypt.verifyChecksum(data);

					if (!checksumOk) {
						_log.warn("Incorrect packet checksum, closing connection (LS)", Config.SERVER_ID);
						return;
					}

					//					if (Config.DEBUG) {
					//						_log.warning("[C]\n" + Util.printData(data));
					//					}

					int packetType = data[0] & 0xff; //-- 获取游戏服务器所发送包类型

					switch (packetType) {
					case 00:
						onReceiveBlowfishKey(data); //-- 接受从GS发送过来的BlowFish包
						break;
					case 01:
						onGameServerAuth(data); //-- 接收服务器的授权请求
						break;
					case 02:
						onReceivePlayerInGame(data); //-- 接收角色进入游戏服务器消息
						break;
					case 03:
						onReceivePlayerLogOut(data); //-- 接收离开游戏的账户
						break;
					case 04:
						onReceiveChangeAccessLevel(data); //-- 改变玩家权限等级,写入数据库
						break;
					case 05:
						onReceivePlayerAuthRequest(data); //-- 接收player请求授权协议
						break;
					case 06:
						onReceiveServerStatus(data); //-- 接收游戏服务器状态
						break;
					//					case 07:
					//						onReceivePlayerCounts(data);  //-- 接受游戏服上当前玩家数量
					//						break;
					case 07:
						onReceiveCharacterCounts(data); //-- 接受游戏服上某账户创建角色数量
						break;
					default:
						_log.warn("Unknown Opcode (" + Integer.toHexString(packetType).toUpperCase() + ") from GameServer, closing connection.", Config.SERVER_ID);
						forceClose(LoginServerFail.NOT_AUTHED); //-- 强关连接
					}

				} catch (SocketTimeoutException e) {
					try {
						if (_socket != null && !_socket.isClosed()) {
							sendPacket(new KeepAlive());
						}
					} catch (Exception ex) {
						try {
							if (_socket != null && !_socket.isClosed()) {
								_in.close();
								_out.close();
								_socket.close();
							}
						} catch (Exception ee) {
							_log.info("close socket fail!", Config.SERVER_ID, ee);
							ee.printStackTrace();
						}
					}
				}
			}
		} catch (Exception e) {
			String serverName = (getServerId() != -1 ? "[" + getServerId() + "] " + _gsi.getExternalHost() : "(" + _connectionIPAddress + ")");
			String msg = "GameServer " + serverName + ": Connection lost: " + e.getMessage();
			_log.info(msg, Config.SERVER_ID, e);
			broadcastToTelnet(msg);
			e.printStackTrace();
		} finally {
			try {
				_in.close();
				_out.close();
				_socket.close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
			if (isAuthed()) {
				_gsi.setDown();
				_log.info("Server [" + getServerId() + "] " + _gsi.getExternalHost() + " is now set as disconnected", Config.SERVER_ID);
			}

			LoginServer.getInstance().getGameServerListener().removeGameServer(this);
			LoginServer.getInstance().getGameServerListener().removeFloodProtection(_connectionIp);
		}
	}

	private void onReceiveBlowfishKey(byte[] data) {

	}

	/**
	 * 接受游戏服上某账户创建角色数量
	 * 
	 * @param data
	 */
	private void onReceiveCharacterCounts(byte[] data) {
		if (isAuthed()) {
			if (_log.isDebugEnabled()) {
				_log.debug("CharacterCounts received");
			}
			@SuppressWarnings("unused")
			CharacterCountsRequest characterCountsRequest = new CharacterCountsRequest(data);

		} else {
			forceClose(LoginServerFail.NOT_AUTHED);
		}
	}

	/**
	 * 处理游戏服务器发过来的注册请求
	 * 
	 * @param data
	 * @throws IOException
	 */
	private void onGameServerAuth(byte[] data) throws IOException {
		GameServerAuth gsa = new GameServerAuth(data);
		if (_log.isDebugEnabled()) {
			_log.debug("Auth request received");
		}
		handleRegProcess(gsa);
		if (isAuthed()) {
			AuthResponse ar = new AuthResponse(getGameServerInfo().getId(), getGameServerInfo().getExternalHost());
			sendPacket(ar);
			if (_log.isDebugEnabled()) {
				_log.debug("Authed: id: " + getGameServerInfo().getId());
			}
			broadcastToTelnet("GameServer [" + getServerId() + "] " + _gsi.getExternalHost() + " is connected");
		}
	}

	/**
	 * 接收角色进入游戏服务器消息
	 * 
	 * @param data
	 */
	private void onReceivePlayerInGame(byte[] data) {
		if (isAuthed()) {
			PlayerInGame pig = new PlayerInGame(data);
			List<String> newAccounts = pig.getAccounts();
			for (String account : newAccounts) {
				_accountsOnGameServer.add(account);
				if (_log.isDebugEnabled()) {
					_log.debug("Account " + account + " logged in GameServer: [" + getServerId() + "] " + _gsi.getExternalHost());
				}

				broadcastToTelnet("Account " + account + " logged in GameServer " + getServerId());
			}

		} else {
			forceClose(LoginServerFail.NOT_AUTHED);
		}
	}

	/**
	 * 接收离开游戏的账户
	 * 
	 * @param data
	 */
	private void onReceivePlayerLogOut(byte[] data) {
		if (isAuthed()) {
			PlayerLogout plo = new PlayerLogout(data);
			_accountsOnGameServer.remove(plo.getAccount());

			if (LoginController.getInstance().isAccountInLoginServer(plo.getAccount())) { //-- 删除登陆客户端列表信息内容。
				LoginController.getInstance().removeAuthedLoginClient(plo.getAccount());

				//-- 登出时候记录当前游戏服务器上玩家数量减1
				GameServerInfo gsi = GameServerTable.getInstance().getRegisteredGameServerById(getServerId());
				if (gsi != null && gsi.getPlayCount() > 0) {
					gsi.setPlayCount(gsi.getPlayCount() - 1);
				}
			}

			if (_log.isDebugEnabled()) {
				_log.debug("Player " + plo.getAccount() + " logged out from gameserver [" + getServerId() + "] " + _gsi.getExternalHost());
			}

			broadcastToTelnet("Player " + plo.getAccount() + " disconnected from GameServer " + getServerId());
		} else {
			forceClose(LoginServerFail.NOT_AUTHED);
		}
	}

	/**
	 * 改变玩家权限等级,写入数据库
	 * 
	 * @param data
	 */
	private void onReceiveChangeAccessLevel(byte[] data) {
		if (isAuthed()) {
			ChangeAccessLevel cal = new ChangeAccessLevel(data);
			LoginController.getInstance().setAccountAccessLevel(cal.getAccount(), cal.getLevel());
			_log.info("Changed " + cal.getAccount() + " access level to " + cal.getLevel(), Config.SERVER_ID);
		} else {
			forceClose(LoginServerFail.NOT_AUTHED);
		}
	}

	/**
	 * 接收player请求授权协议
	 * 
	 * @param data
	 * @throws IOException
	 */
	private void onReceivePlayerAuthRequest(byte[] data) throws IOException {
		if (isAuthed()) {
			PlayerAuthRequest par = new PlayerAuthRequest(data);
			PlayerAuthResponse authResponse;
			if (_log.isDebugEnabled()) {
				_log.debug("auth request received for Player " + par.getAccount());
			}

			int key = LoginController.getInstance().getKeyForAccount(par.getAccount());

			//验证用户在游戏服务器上提交的sessionKey是否和登录服务器发给他的相同
			if (key == par.getKey()) {
				if (_log.isDebugEnabled()) {
					_log.debug("auth request: OK");
				}
				
				authResponse = new PlayerAuthResponse(par.getAccount(), true, LoginController.getInstance().getChannelIDForAccount(par.getAccount()));

				//从授权登录池中删除对象
				LoginController.getInstance().removeAuthedLoginClient(par.getAccount());

				

				//-- 登入时候记录当前游戏服务器上玩家数量增1
				GameServerInfo gsi = GameServerTable.getInstance().getRegisteredGameServerById(getServerId());
				if (gsi != null) {
					gsi.setPlayCount(gsi.getPlayCount() + 1);
				}

			} else {
				if (_log.isDebugEnabled()) {
					_log.debug("auth request: NO");
					_log.debug("session key from self: " + key);
					_log.debug("session key sent: " + par.getKey());
				}
				authResponse = new PlayerAuthResponse(par.getAccount(), false, LoginController.getInstance().getChannelIDForAccount(par.getAccount()));
			}
			sendPacket(authResponse); //-- 发送玩家鉴权包
		} else {
			forceClose(LoginServerFail.NOT_AUTHED);
		}
	}

	/**
	 * 处理游戏服务器发来的服务器状态
	 * 
	 * @param data
	 */
	private void onReceiveServerStatus(byte[] data) {
		if (isAuthed()) {
			if (_log.isDebugEnabled()) {
				_log.debug("ServerStatus received");
			}
			new ServerStatus(data, getServerId()); //will do the actions by itself
		} else {
			forceClose(LoginServerFail.NOT_AUTHED);
		}
	}

	/**
	 * 处理注册请求
	 * 
	 * 如果请求的ID已经被占用，但是占用方没有通过授权，则可以顶替占用方
	 * 
	 * 否则如果登录服务器接受新注册操作且游戏服务器支持ID的修改，那么授权
	 * 
	 * @param gameServerAuth
	 */
	private void handleRegProcess(GameServerAuth gameServerAuth) {
		GameServerTable gameServerTable = GameServerTable.getInstance();
		int id = gameServerAuth.getDesiredID();
		//检查是否有服务器已经注册了此ID
		GameServerInfo gsi = gameServerTable.getRegisteredGameServerById(id);

		if (gsi != null) {
			synchronized (gsi) {
				if (gsi.isAuthed()) {//如果此ID已经被占用，且占用的服务器已经通过了授权
					forceClose(LoginServerFail.REASON_ALREADY_LOGGED8IN);
				} else {
					//检查游戏服务器发送的内网IP是否正确，否则修正
					String internalIP = gsi.getInternalIp() == null || gsi.getInternalIp().equals("*") ? _connectionIp : gsi.getInternalIp();

					if (internalIP.equals(gameServerAuth.getInternalIP())) {//验证和注册时的内网IP是否一致，不一致则拒接
						//把此游戏服务器信息更新成新请求的游戏服务器
						attachGameServerInfo(gsi, gameServerAuth);
					} else {
						forceClose(LoginServerFail.REASON_WRONG_HEXID);
					}
				}
			}
		} else {
			if (Config.ACCEPT_NEW_GAMESERVER && gameServerAuth.acceptAlternateID()) {
				gsi = new GameServerInfo(id, this);
				if (gameServerTable.registerWithFirstAvaliableId(gsi)) {//向游戏服务器列表注册游戏服务器
					//把此游戏服务器信息更新成新请求的游戏服务器
					attachGameServerInfo(gsi, gameServerAuth);
					//把此游戏服务器写入数据库
					gameServerTable.registerServerOnDB(gsi);
				} else {
					forceClose(LoginServerFail.REASON_NO_FREE_ID);
				}
			} else {
				forceClose(LoginServerFail.REASON_WRONG_HEXID);
			}
		}
	}

	public boolean hasAccountOnGameServer(String account) {
		return _accountsOnGameServer.contains(account);
	}

	public int getPlayerCount() {
		return _accountsOnGameServer.size();
	}

	/**
	 * 使用游戏服务器授权信息包配置游戏服务器信息对象
	 * 
	 * @param gsi
	 *            The GameServerInfo to be attached.
	 * @param gameServerAuth
	 *            The server info.
	 */
	private void attachGameServerInfo(GameServerInfo gsi, GameServerAuth gameServerAuth) {
		setGameServerInfo(gsi);
		gsi.setGameServerThread(this);
		gsi.setPort(gameServerAuth.getPort());
		setGameHosts(gameServerAuth.getExternalHost(), gameServerAuth.getExternalIP(), gameServerAuth.getInternalIP(), gameServerAuth.getServerType());
		gsi.setMaxPlayers(gameServerAuth.getMaxPlayers());
		//授权
		gsi.setAuthed(true);
	}

	private void forceClose(int reason) {
		LoginServerFail lsf = new LoginServerFail(reason);
		try {
			sendPacket(lsf);
		} catch (IOException e) {
			_log.error("GameServerThread: Failed kicking banned server. Reason: " + e.getMessage(), Config.SERVER_ID, e);
		}

		try {
			_socket.close();
		} catch (IOException e) {
			_log.error("GameServerThread: Failed disconnecting banned server, server already disconnected.", Config.SERVER_ID, e);
		}
	}

	/**
	 * @param ipAddress
	 * @return
	 */
	public static boolean isBannedGameserverIP(String ipAddress) {
		return false;
	}

	public GameServerThread(Socket con) {
		_socket = con;
		_connectionIp = con.getInetAddress().getHostAddress();
		try {
			_in = _socket.getInputStream();
			_socket.setSoTimeout(3 * 1000);
			_socket.setSoLinger(true, 3); //-- 设置发送超时时间3秒
			_out = new BufferedOutputStream(_socket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}

		start();
	}

	/**
	 * @param sl
	 * @throws IOException
	 */
	private void sendPacket(ServerBasePacket sl) throws IOException {
		byte[] data = sl.getContent();
		NewCrypt.appendChecksum(data);
		if (_log.isDebugEnabled()) {
			_log.debug("[S] " + sl.getClass().getSimpleName() + ":\n" + Util.printData(data));
		}

		int len = data.length + 2;
		synchronized (_out) {//由http或socket包内LoginPacketHelper类内部的threadPool调用或GameServerThread本身调用所以要同步
			_out.write(len & 0xff);
			_out.write(len >> 8 & 0xff);
			_out.write(data);
			_out.flush();
		}
	}

	private void broadcastToTelnet(String msg) {
		if (LoginServer.getInstance().getStatusServer() != null) {
			LoginServer.getInstance().getStatusServer().sendMessageToTelnets(msg);
		}
	}

	public void kickPlayer(String account) {
		KickPlayer kp = new KickPlayer(account);
		try {
			sendPacket(kp);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param gameHost
	 *            The gameHost to set.
	 */
	public void setGameHosts(String gameExternalHost, String gameExternalIP, String gameInternalIP, int type) {
		String oldInternal = _gsi.getInternalIp();
		String oldExternal = _gsi.getExternalIp();
		String oldExternalName = _gsi.getExternalHost();
		int oldType = _gsi.getType();

		_gsi.setExternalHost(gameExternalHost);
		_gsi.setType(type);

		if (!gameExternalIP.equals("*")) {
			try {
				_gsi.setExternalIp(InetAddress.getByName(gameExternalIP).getHostAddress());
			} catch (UnknownHostException e) {
				_log.error("Couldn't resolve hostname \"" + gameExternalIP + "\"", Config.SERVER_ID, e);
			}
		} else {
			_gsi.setExternalIp(_connectionIp);
		}
		if (!gameInternalIP.equals("*")) {
			try {
				_gsi.setInternalIp(InetAddress.getByName(gameInternalIP).getHostAddress());
			} catch (UnknownHostException e) {
				_log.error("Couldn't resolve hostname \"" + gameInternalIP + "\"", Config.SERVER_ID, e);
			}
		} else {
			_gsi.setInternalIp(_connectionIp);
		}

		//如果游戏服务器的信息发生了修改则信息回写数据库
		if (oldExternal != _gsi.getExternalIp() || oldExternalName.equals(_gsi.getExternalHost()) || oldType != _gsi.getType()) {
			GameServerTable.getInstance().updateServerOnDB(_gsi);
		}

		_log.info("Updated Gameserver [" + getServerId() + "] " + _gsi.getExternalHost() + " IP's:", Config.SERVER_ID);
		if (oldInternal == null || !oldInternal.equalsIgnoreCase(gameInternalIP))
			_log.info("InternalIP: " + gameInternalIP, Config.SERVER_ID);
		if (oldExternal == null || !oldExternal.equalsIgnoreCase(gameExternalIP))
			_log.info("ExternalIP: " + gameExternalIP, Config.SERVER_ID);
	}

	/**
	 * 返回此连接维护的游戏服务器在登录服务器上是否已授权
	 * 
	 * @return Returns the isAuthed.
	 */
	public boolean isAuthed() {
		if (getGameServerInfo() == null)
			return false;
		return getGameServerInfo().isAuthed();
	}

	public void setGameServerInfo(GameServerInfo gsi) {
		_gsi = gsi;
	}

	public GameServerInfo getGameServerInfo() {
		return _gsi;
	}

	/**
	 * @return Returns the connectionIpAddress.
	 */
	public String getConnectionIpAddress() {
		return _connectionIPAddress;
	}

	private int getServerId() {
		if (getGameServerInfo() != null) {
			return getGameServerInfo().getId();
		}
		return -1;
	}
}
